﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using ObviousCode.UI.Aspect.DevHack;
using System.Reflection;
using ObviousCode.UI.Aspect.Plugins;
using ObviousCode.UI.Aspect.PluginLibrary.Interfaces;
using ObviousCode.Common.Utils;
using ObviousCode.Common.Utils.Library.Exceptions;
using ObviousCode.Aspect.PluginLibrary.Interfaces;
using ObviousCode.Aspect.PluginLibrary;
using ObviousCode.UI.Aspect.PluginLibrary;
using ObviousCode.Common.Utils.Library;
using ObviousCode.Aspect.PluginLibrary.Abstract;
using ObviousCode.Aspect.PluginLibrary.Themes;
using ObviousCode.Aspect.PluginLibrary.Descriptors;
using ObviousCode.UI.Aspect.DefaultPlugins.Arrow;
using ObviousCode.Common.Utils.Library.Collections;
using ObviousCode.Aspect.PluginLibrary.Event;
using ObviousCode.Common.Utils.Library.Dictionaries;
using System.IO;
using ObviousCode.Aspect.PluginLibrary.Caches;
using ObviousCode.UI.Aspect.Persistence.AspectDesigner.Savers;
using ObviousCode.UI.Aspect.Persistence.AspectDesigner.Loaders;
using ObviousCode.UI.Aspect.PluginLibrary.Collections;
using ObviousCode.UI.Aspect.Events;
using ObviousCode.Utils.UI.Progress;
using ObviousCode.Common.Utils.UI;
using ObviousCode.UI.Aspect.PluginLibrary.BaseClasses;
using ObviousCode.UI.Aspect.Resources;
using Microsoft.Win32;

namespace ObviousCode.UI.Aspect
{
    public partial class AspectDesigner : Form, IAspectDesigner
    {
        #region Fields (5)     
         
        const string regKey = @"HKEY_CURRENT_USER\Software\ObviousCode\Aspect\Settings";

        bool _registering;

        bool _isDesignMode;

        ProgressForm _progress;        
        
        Hierarchy<string> _categories;                

        #endregion Fields

        #region Constructors (1)

        public AspectDesigner()
        {
            _registering = false;

            Extension = "srf";

            ExtensionDescription = "Aspect Surface files";

            InitializeComponent();

            _isDesignMode = LicenseManager.UsageMode == LicenseUsageMode.Designtime;

            if (_isDesignMode) return;

            _categories = new Hierarchy<string>();
            
            AspectCache.Main.LoadSurfaceObjectFactories(PluginLoader.LoadPluginsFromAppConfig(this));
            AspectCache.Main.LoadSurfaceObjectRelationshipFactories(PluginLoader.LoadRelationshipPluginsFromAppConfig(this));            

            _menu.NodeCreating += new ObviousCode.Common.Utils.UI.NodeCreatingEventHandler(_menu_NodeCreating);          

            RegisterDefaultRelationship<ArrowFactory>();

            //Set arrow behaviour tool strip visibility by forcing event
            _surface.DrawArrowsOnRightClick = _surface.DrawArrowsOnRightClick;

            SaveWindowState = true;
        }

        void _menu_NodeCreating(object sender, ObviousCode.Common.Utils.UI.TreeNodeCreatingEventArgs e)
        {
            if (MenuNodeCreating != null)
            {
                MenuNodeCreating(sender, e);
            }
        }        

        #endregion Constructors

        #region Properties (3)

        public string Extension { get; set; }

        public string ExtensionDescription { get; set; }

        public bool ShowMenuNewAsDropDown { get { return _menu.ShowMenuNewAsDropDown; } set { _menu.ShowMenuNewAsDropDown = value; } }

        bool SaveWindowState 
        {
            get
            {                
                string settingsValue = @"SaveWindowState";                

                object reg = Registry.GetValue(regKey, settingsValue, 1);

                return ((int)reg) == 1;
            }
            set
            {                
                string settingsValue = @"SaveWindowState";

                Registry.SetValue(regKey, settingsValue, value ? 1 : 0, RegistryValueKind.DWord);
            }
        }

        #endregion Properties

        #region Delegates and Events (4)

        // Events (4) 

        public event EventHandler SaveCompleted;

        public event EventHandler<AspectSaveCompletingEventArgs> SaveCompleting;

        public event EventHandler<AspectSaveExceptionEventArgs> SaveException;

        public event EventHandler<AspectSaveInitiatedEventArgs> SaveInitiated;

        #endregion Delegates and Events

        #region Methods (42)

        // Public Methods (15) 

        public void AddCategory(string categoryName)
        {
            _categories.Add(categoryName);
        }

        public void AddCategory(string categoryName, string parentCategoryName)
        {
            _categories.Add(categoryName, parentCategoryName);
        }

        public void BeginRegistration()
        {
            _registering = true;
        }

        public void EndRegistration()
        {
            _registering = false;
            RebuildControls();
        }

        public T GetPersistable<T>() where T : IAspectPersistable
        {
            if (typeof(T) == typeof(AspectSurface) ||
                typeof(T) == typeof(IAspectSurface)
                )
            {
                return (T)(IAspectComponent)_surface;//yucky contra-variance
            }
            else if (
                typeof(T) == typeof(AspectMenu) ||
                typeof(T) == typeof(IAspectMenu)

                )
            {
                return (T)(IAspectComponent)_menu;
            }
            else if (
                typeof(T) == typeof(IAspectCache) ||
                typeof(T) == typeof(AspectCache)
                )
            {
                return (T)(IAspectPersistable)AspectCache.Main;
            }

            throw ExceptionBuilder.Get("Type {0} not accessible from AspectDesigner.", typeof(T));
        }

        public ISurfaceObjectFactory GetFactory<T>() where T : ISurfaceObjectFactory
        {
            return AspectCache.Main.GetSurfaceObjectFactory<T>();
        }

        public void RegisterDefaultRelationship<TRelationshipFactory>()
             where TRelationshipFactory : ISurfaceRelationshipFactory
        {
            RegisterDefaultRelationship<TRelationshipFactory>("default", new BaseAspectRelationshipFactory(), new BasicRelationshipTheme());
        }

        public void RegisterDefaultRelationship<TRelationshipFactory>(IAspectRelationshipFactory create)
             where TRelationshipFactory : ISurfaceRelationshipFactory
        {
            RegisterDefaultRelationship<TRelationshipFactory>("default", create, new BasicRelationshipTheme());
        }

        public void RegisterDefaultRelationship<TRelationshipFactory>(string description, IAspectRelationshipFactory create)
             where TRelationshipFactory : ISurfaceRelationshipFactory
        {
            RegisterDefaultRelationship<TRelationshipFactory>(description, create, new BasicRelationshipTheme());
        }

        public ISurfaceObjectFactory RegisterObjects<TControlFactory>(string key, IAspectItemFactory create)
             where TControlFactory : ISurfaceObjectFactory
        {
            return RegisterObjects<TControlFactory>(key, key, create, new ChartObjectImages(), new BasicTheme());
        }

        public ISurfaceObjectFactory RegisterObjects<TControlFactory>(string key, string description, IAspectItemFactory create)
             where TControlFactory : ISurfaceObjectFactory
        {
            return RegisterObjects<TControlFactory>(key, description, create, new ChartObjectImages(), new BasicTheme());
        }

        public ISurfaceObjectFactory RegisterObjects<TControlFactory>(string key, string description, IAspectItemFactory create, ChartObjectImages images)
             where TControlFactory : ISurfaceObjectFactory
        {
            return RegisterObjects<TControlFactory>(key, description, create, images ?? new ChartObjectImages(), new BasicTheme());
        }

        public ISurfaceObjectFactory RegisterObjects<TControlFactory>(string key, string description,
            IAspectItemFactory create, ChartObjectImages images, IUITheme theme)
             where TControlFactory : ISurfaceObjectFactory
        {
            ISurfaceObjectFactory factory = AspectCache.Main.GetSurfaceObjectFactory<TControlFactory>();

            if (factory == null)
            {
                ExceptionBuilder.Format("Plug-in Factory \"{0}\" is not found", typeof(TControlFactory).ToString());
            }

            AspectCache.Main.Add(create);

            factory.RegisterSurfaceItemDescriptor(
                key, new SurfaceItemDescriptor(
                    key,
                    description,
                    images ?? new ChartObjectImages(),
                    theme ?? new BasicTheme(),
                    create
                    ));

            if (!_registering)
            {
                RebuildControls();
            }


            return factory;

        }

        public ISurfaceRelationshipFactory RegisterRelationship<TRelationshipFactory>(string key, IAspectRelationshipFactory create)
             where TRelationshipFactory : ISurfaceRelationshipFactory
        {
            return RegisterRelationship<TRelationshipFactory>(key, key, create, new BasicRelationshipTheme());
        }

        public ISurfaceRelationshipFactory RegisterRelationship<TRelationshipFactory>(string key, string description, IAspectRelationshipFactory create)
             where TRelationshipFactory : ISurfaceRelationshipFactory
        {
            return RegisterRelationship<TRelationshipFactory>(key, key, create, new BasicRelationshipTheme());
        }
        // Private Methods (27)                                  
        

        private bool Clear()
        {
            bool _dirtySurface = _surface.TestForDirtyData();

            if (_dirtySurface || _menu.HasDirtyData)
            {
                if (!NotifyUserOfDirtyDataBeforeClear())
                {
                    return false;
                }
            }

            if (_surface != null)
            {
                _surface.Reset();
                ClearDirtyData();
            }

            RebuildControls();
            return true;
        }

        private void ClearDirtyData()
        {
            _menu.ClearDirtyData();
            _surface.InformOnSave();
        }        

        private string GetSaveData()
        {
            DesignerSaver saver = DesignerSaver.LatestVersion;

            return saver.Save(this).PersistToString();
        }

        private void LoadDesigner()
        {
            OpenFileDialog open = new OpenFileDialog();

            open.DefaultExt = Extension;
            open.Filter = string.Format("{0}|{1}", ExtensionDescription, string.Format("*.{0}", Extension));

            if (Clear())
            {
                if (open.ShowDialog() == DialogResult.OK)
                {
                    DesignerLoader loader = null;
                    try
                    {
                        _progress = new ProgressForm();

                        _progress.Text = "Loading";

                        _progress.StatusFormatString = "Initialising (Step {0} of {1})";
                        
                        _progress.Show();

                        _progress.Total = DesignerLoader.LatestVersion.LoadSteps;

                        Application.DoEvents();

                        loader = DesignerLoader.LatestVersion;

                        loader.StepCommencing += new EventHandler<ObviousCode.Common.Utils.Progress.ProgressStepEventArgs>(LatestVersion_StepCommencing);
                        
                        loader.StepCompleted += new EventHandler<ObviousCode.Common.Utils.Progress.ProgressStepEventArgs>(LatestVersion_StepCompleted);                        

                        loader.Load(this, open.OpenFile());
                    }                    
                    finally
                    {
                        loader.StepCommencing -= new EventHandler<ObviousCode.Common.Utils.Progress.ProgressStepEventArgs>(LatestVersion_StepCommencing);

                        loader.StepCompleted -= new EventHandler<ObviousCode.Common.Utils.Progress.ProgressStepEventArgs>(LatestVersion_StepCompleted);

                        _progress.Close();

                        _progress.Dispose();
                    }
                    
                    ClearDirtyData();
                }
            }
        }

        void LatestVersion_StepCompleted(object sender, ObviousCode.Common.Utils.Progress.ProgressStepEventArgs e)
        {           
            ReportStep(e);
        }

        void LatestVersion_StepCommencing(object sender, ObviousCode.Common.Utils.Progress.ProgressStepEventArgs e)
        {          
            _progress.Current++;
          
            ReportStep(e);
        }

        private void ReportStep(ObviousCode.Common.Utils.Progress.ProgressStepEventArgs e)
        {
            Application.DoEvents();
            
            _progress.Text = e.StepName;

            _progress.StatusFormatString = string.Format("{0} {1}", e.Message, "(Step {0} of {1})");

            Application.DoEvents();
        }

        private void newToolStripButton_Click(object sender, EventArgs e)
        {
            Clear();
        }

        //returns true if Cancel not hit
        private bool NotifyUserOfDirtyDataBeforeClear()
        {
            DialogResult result =
                MessageBox.Show(this, "There are unsaved changes. Do you want to save before continuing?", "Save Changes?", MessageBoxButtons.YesNoCancel, MessageBoxIcon.Exclamation, MessageBoxDefaultButton.Button1);

            switch (result)
            {
                case DialogResult.No:
                    return true;
                case DialogResult.Yes:
                    SaveDesigner();
                    return true;
                default:
                    return false;
            }
        }

        private void openToolStripButton_Click(object sender, EventArgs e)
        {
            LoadDesigner();
        }

        private void RebuildControls()
        {
            _menu.Rebuild(_categories);            
        }

        private void RegisterDefaultRelationship<TRelationshipFactory>(string description, IAspectRelationshipFactory create, IUIRelationshipTheme theme)
             where TRelationshipFactory : ISurfaceRelationshipFactory
        {
            AspectCache.Main.DefaultRelationshipFactory = RegisterRelationship<TRelationshipFactory>("default", description, create, theme);            
        }

        public void RegisterDefaultRelationshipTheme(IUIRelationshipTheme theme)
        {
            AspectCache.Main.DefaultRelationshipFactory.Descriptors["default"].Theme = theme;
        }

        private ISurfaceRelationshipFactory RegisterRelationship<TRelationshipFactory>(string key, string description, 
            IAspectRelationshipFactory aspectRelationshipFactory, IUIRelationshipTheme theme) 
            where TRelationshipFactory : ISurfaceRelationshipFactory
        {
            ISurfaceRelationshipFactory factory = AspectCache.Main.GetRelationshipFactory<TRelationshipFactory>();

            if (factory == null)
            {
                ExceptionBuilder.Format("Relationship Plugin Factory \"{0}\" is not found", typeof(TRelationshipFactory).ToString());
            }

            AspectCache.Main.Add(aspectRelationshipFactory);

            factory.RegisterSurfaceRelationshipDescriptor(key, new SurfaceRelationshipDescriptor(
                key,
                description,
                theme,
                aspectRelationshipFactory
                ));

            return factory;
        }

        private bool ReportSaveBegin()
        {
            AspectSaveInitiatedEventArgs e = new AspectSaveInitiatedEventArgs();

            if (SaveInitiated != null)
            {
                SaveInitiated(this, e);
            }

            return !e.Cancel;
        }

        private bool ReportSaveCompleted()
        {
            AspectSaveCompletedEventArgs e = new AspectSaveCompletedEventArgs();

            if (SaveCompleted != null)
            {
                SaveCompleted(this, e);
            }

            return !e.ReportHandled;
        }

        private AspectSaveCompletingEventArgs ReportSaveCompleting(string saveData)
        {
            AspectSaveCompletingEventArgs e = new AspectSaveCompletingEventArgs(saveData);

            e.Extension = Extension;

            e.ExtensionDescription = ExtensionDescription;

            if (SaveCompleting != null)
            {
                SaveCompleting(this, e);
            }

            return e;
        }

        private void SaveDesigner()
        {
            if (ReportSaveBegin())
            {
                string saveData = GetSaveData();

                AspectSaveCompletingEventArgs e = ReportSaveCompleting(saveData);

                if (e.Handled) return;

                SaveFileDialog save = new SaveFileDialog();

                save.Filter = string.Format("{0}|*.{1}", ExtensionDescription, Extension);
                save.DefaultExt = Extension;
                save.AddExtension = true;

                if (save.ShowDialog() != DialogResult.Cancel)
                {
                    ClearDirtyData();

                    using (FileStream stream = new FileStream(save.FileName, FileMode.Create, FileAccess.Write))
                    {
                        using (TextWriter writer = new StreamWriter(stream))
                        {
                            try
                            {
                                writer.Write(saveData);
                            }
                            catch (System.Exception ex)
                            {
                                AspectSaveExceptionEventArgs ea = new AspectSaveExceptionEventArgs(ex);
                                if (SaveException != null)
                                {
                                    SaveException(this, ea);
                                }

                                if (!ea.Handled)
                                {
                                    MessageBox.Show("Sorry, but there was an error during save. {0}", ex.Message);
                                }
                            }
                        }
                    }

                    if (ReportSaveCompleted())
                    {
                        MessageBox.Show("Save Completed.");
                    }
                }
            }
        }

        private void saveToolStripButton_Click(object sender, EventArgs e)
        {
            SaveDesigner();
        }

        private void unimplemented_Click(object sender, EventArgs e)
        {
            MessageBox.Show("This feature is currently unimplemented");
        }

        #endregion Methods

        public event NodeCreatingEventHandler MenuNodeCreating;
        
        private void AspectDesigner_KeyDown(object sender, KeyEventArgs e)
        {
            if (e.KeyCode == Keys.A)
            {
                if (e.Control)
                {
                    if (e.Alt || e.Shift)
                    {
                        _surface.SelectNextGroup();
                    }
                    else
                    {                        
                        _surface.SetSelectionOfAll(true, true);
                    }
                }
            }
            else if ((int)e.KeyCode == 219)
            {
                _surface.SelectLastSurfaceObject();
            }
            else if ((int)e.KeyCode == 221)
            {
                _surface.SelectNextSurfaceObject();
            }
            
        }

        private void AspectDesigner_Shown(object sender, EventArgs e)
        {
            if (StartupTips.CanBeShown)
            {
                string csv = Tips.DefaultTips;

                List<string> values = new List<string>(csv.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries));

                using (StartupTips tips = new StartupTips(values))
                {
                    tips.ShowNavigation = true;
                    tips.ShowDialog(this);
                }

            }
        }

        private void AspectDesigner_Load(object sender, EventArgs e)
        {
            RestoreWindowStateIfRequired();
        }

        private void RestoreWindowStateIfRequired()
        {
            if (SaveWindowState)
            {
                string key = string.Format(@"{0}\{1}", regKey, "WindowState");

                string locXValueKey = "LocX";
                string locYValueKey = "LocY";
                string widthValueKey = "Width";
                string heightValueKey = "Height";
                string stateKeyValueKey = "FormState";

                int locX = GetRegistryInt(key, locXValueKey, 0);
                int locY = GetRegistryInt(key, locYValueKey, 0);

                int sizeW = GetRegistryInt(key, widthValueKey, 1024);
                int sizeH = GetRegistryInt(key, heightValueKey, 768);

                this.Size = new Size(sizeW, sizeH);
                this.Location = new Point(locX, locY);

                WindowState = (FormWindowState)GetRegistryInt(key, stateKeyValueKey, (int)FormWindowState.Maximized);
            }
        }

        private void SaveWindowStateIfRequired()
        {
            if (SaveWindowState)
            {
                string key = string.Format(@"{0}\{1}", regKey, "WindowState");

                string locXValueKey = "LocX";
                string locYValueKey = "LocY";
                string widthValueKey = "Width";
                string heightValueKey = "Height";
                string stateKeyValueKey = "FormState";

                Registry.SetValue(key, locXValueKey, Location.X);
                Registry.SetValue(key, locYValueKey, Location.Y);

                Registry.SetValue(key, widthValueKey, Size.Width);
                Registry.SetValue(key, heightValueKey, Size.Height);

                Registry.SetValue(key, stateKeyValueKey, (int)WindowState);
            }
        }

        private int GetRegistryInt(string key,string value,int defaultValue)
        {
 	        try
 	        {
                object regValue = Registry.GetValue(key, value, defaultValue);

                return Int32.Parse(regValue == null ? "" : regValue.ToString());
 	        }
 	        catch (System.Exception ex)
 	        {
 	        	Registry.SetValue(key, value, defaultValue);
                return defaultValue;
 	        }
        }

        private void AspectDesigner_FormClosing(object sender, FormClosingEventArgs e)
        {
            SaveWindowStateIfRequired();
        }        
    }
}